package com.yonyougov.devops.monitor.service.impl;

import com.yonyougov.devops.monitor.dao.*;
import com.yonyougov.devops.monitor.dto.HardwareInfoRequestDto;
import com.yonyougov.devops.monitor.entity.*;
import com.yonyougov.devops.monitor.enums.HardwareType;
import com.yonyougov.devops.monitor.service.ServerInfoService;
import com.yonyougov.devops.monitor.util.EmailSender;
import com.yonyougov.devops.monitor.util.IdGenerator;
import com.yonyougov.devops.monitor.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ServerInfoServiceImpl implements ServerInfoService {

    @Value("${monitor.mail.receiver}")
    private String receiver;
    @Value("${monitor.threshold.cpu}")
    private double cpuThreshold;
    @Value("${monitor.threshold.memory}")
    private double memoryThreshold;
    @Value("${monitor.threshold.disk}")
    private double diskThreshold;
    @Value("${monitor.threshold.duration}")
    private int duration;

    private final static String PREFIX = "crux-";

    private ServerInfoRepository serverInfoRepository;
    private MemInfoRepository memInfoRepository;
    private DiskRepository diskRepository;
    private NetInterfaceRepository netInterfaceRepository;
    private CpuInfoRepository cpuInfoRepository;
    private EmailSender emailSender;
    private StringRedisTemplate stringRedisTemplate;

    public ServerInfoServiceImpl(ServerInfoRepository serverInfoRepository, MemInfoRepository memInfoRepository,
                                 DiskRepository diskRepository, NetInterfaceRepository netInterfaceRepository,
                                 CpuInfoRepository cpuInfoRepository, EmailSender emailSender,
                                 StringRedisTemplate stringRedisTemplate) {
        this.serverInfoRepository = serverInfoRepository;
        this.memInfoRepository = memInfoRepository;
        this.diskRepository = diskRepository;
        this.netInterfaceRepository = netInterfaceRepository;
        this.cpuInfoRepository = cpuInfoRepository;
        this.emailSender = emailSender;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public void addServerInfo(ServerInfoVo serverInfoVo) {
        Date date = new Date();
        handleServerInfo(date, serverInfoVo);
        handleCpuInfo(date, serverInfoVo.getHostname(), serverInfoVo.getCpuInfo());
        handleMemInfo(date, serverInfoVo.getHostname(), serverInfoVo.getMemInfo());
        //handleNetInfo(date, serverInfoVo.getHostname(), serverInfoVo.getNetInfo());
        handleDiskInfo(date, serverInfoVo.getHostname(), serverInfoVo.getDiskInfo());
    }

    private void handleDiskInfo(Date date, String hostname, List<DiskVo> diskInfos) {
        if (diskInfos == null || diskInfos.size() == 0) {
            return;
        }
        List<Disk> disks = diskInfos.stream().map(d -> {
            Disk disk = new Disk();
            BeanUtils.copyProperties(d, disk);
            disk.setId(IdGenerator.generate());
            disk.setHostname(hostname);
            disk.setDate(date);

            if (disk.getUsedRate() > diskThreshold) {
                handleAlarm(HardwareType.DISK.name(), hostname, date, diskThreshold, disk.getLabel());
            }

            return disk;
        }).collect(Collectors.toList());
        diskRepository.saveAll(disks);
    }

    private void handleNetInfo(Date date, String hostname, List<NetVo> netInfos) {
        if (netInfos == null || netInfos.size() == 0) {
            return;
        }
        List<NetInterface> netInterfaces = netInfos.stream().map(s -> {
            NetInterface netInterface = new NetInterface();
            BeanUtils.copyProperties(s, netInterface);
            netInterface.setHostname(hostname);
            netInterface.setId(IdGenerator.generate());
            netInterface.setDate(date);
            return netInterface;
        }).collect(Collectors.toList());
        netInterfaceRepository.saveAll(netInterfaces);
    }

    private void handleMemInfo(Date date, String hostname, MemInfoVo info) {
        MemInfo memInfo = new MemInfo();
        BeanUtils.copyProperties(info, memInfo);
        memInfo.setHostname(hostname);
        memInfo.setId(IdGenerator.generate());
        memInfo.setDate(date);
        memInfoRepository.save(memInfo);

        if (info.getUsedRate() < memoryThreshold) {
            return;
        }
        handleAlarm(HardwareType.MEMORY.name(), hostname, date, memoryThreshold, HardwareType.MEMORY.name());
    }


    private void handleCpuInfo(Date date, String hostname, CpuInfoVo cpuInfo) {
        CpuInfo info = new CpuInfo();
        BeanUtils.copyProperties(cpuInfo, info);
        info.setHostname(hostname);
        info.setId(IdGenerator.generate());
        info.setDate(date);
        cpuInfoRepository.save(info);

        if (info.getUsedRate() < cpuThreshold) {
            return;
        }
        handleAlarm(HardwareType.CPU.name(), hostname, date, cpuThreshold, HardwareType.CPU.name());
    }

    private void handleServerInfo(Date date, ServerInfoVo serverInfoVo) {
        ServerInfo serverInfo = new ServerInfo();
        BeanUtils.copyProperties(serverInfoVo, serverInfo);
        serverInfo.setDate(date);
        serverInfoRepository.save(serverInfo);
    }

    private void handleAlarm(String hardwareType, String hostname, Date date, double threshold, String label) {
        String key = PREFIX + hostname + hardwareType + label;
        List<String> data = stringRedisTemplate.opsForList().range(key, 0, -1);
        if (data == null) {
            data = new ArrayList<>();
        }

        if (data.size() > 0) {
            if (date.getTime() - Long.parseLong(data.get(data.size() - 1)) > 120000) {
                stringRedisTemplate.delete(key);
                data = new ArrayList<>();
            }
        }
        data.add(date.getTime() + "");
        stringRedisTemplate.opsForList().rightPush(key, date.getTime() + "");
        if (data.size() < duration) {
            return;
        }
        Runnable r = () -> {
            String name = hardwareType.equals(label) ? "" : (label + "目录");
            String subject = "【监控报警】服务器" + hostname + "的" + hardwareType + "  " + name + "利用率过高";
            String text = hostname + "的" + hardwareType + "  " + name +
                    "利用率过高，持续" + duration + "分钟超过：" + threshold + "%";
            emailSender.sendEmail(receiver.split(","), subject, text);
        };
        new Thread(r).start();

        log.info("{} " + label + " 利用率告警邮件已发送", hostname);
        stringRedisTemplate.delete(key);
    }

    @Override
    public Map<String, Object> findHardwareInfo(HardwareInfoRequestDto dto) {
        Map<String, Object> map = new HashMap<>();
        if ("".equals(dto.getHostname())) {
            map.put(HardwareType.CPU.name(), new ArrayList<>());
            map.put(HardwareType.MEMORY.name(), new ArrayList<>());
            map.put(HardwareType.DISK.name(), new ArrayList<>());
            return map;
        }

        map.put(HardwareType.CPU.name(), findCpuInfo(dto.getHostname(), dto.getStartDate().getTime(), dto.getEndDate().getTime()));
        map.put(HardwareType.MEMORY.name(), findMemInfo(dto.getHostname(), dto.getStartDate().getTime(), dto.getEndDate().getTime()));
        map.put(HardwareType.DISK.name(), findDiskInfo(dto.getHostname(), dto.getStartDate().getTime(), dto.getEndDate().getTime()));
        return map;
    }

    private Object findDiskInfo(String hostname, long startDate, long endDate) {
        return diskRepository.findAllByHostnameAndDateBetweenOrderByDateAsc(hostname, startDate, endDate);
    }

    private Object findMemInfo(String hostname, long startDate, long endDate) {
        return memInfoRepository.findAllByHostnameAndDateBetweenOrderByDateAsc(hostname, startDate, endDate);
    }

    private Object findCpuInfo(String hostname, long startDate, long endDate) {
        return cpuInfoRepository.findAllByHostnameAndDateBetweenOrderByDateAsc(hostname, startDate, endDate);
    }
}
